<?php
/**
 * Simple PHP Contact Form
 *
 * This file is part of the Simple PHP Contact Form which is subject to the New
 * BSD License {@see LICENSE} which you would be advised to read before using,
 * modifying or redistributing this software.
 *
 * PHP version 5.2
 *
 * @category  Simple-PHP-Contact-Form
 * @package   Application_Library
 * @author    jah <jah@jahboite.co.uk>
 * @copyright 2010 jah <jah@jahboite.co.uk>
 * @license   New BSD License {@see LICENSE}
 * @version   SVN: $Id$
 * @link      http://code.google.com/p/simple-php-contact-form/
 */


/**
 * Custom_Validate_Charset.  This validator ensures that the characters which
 * make-up the input string are those defined in a whitelist of acceptable
 * characters.  The input string is first canonicalised before validation begins to
 * ensure that the validator cannot be bypassed.
 *
 * @category  Simple-PHP-Contact-Form
 * @package   Application_Library
 * @author    jah <jah@jahboite.co.uk>
 * @copyright 2010 jah <jah@jahboite.co.uk>
 * @license   New BSD License {@see LICENSE}
 * @version   Release: @package_version@
 * @link      http://code.google.com/p/simple-php-contact-form/
 */
class Custom_Validate_Charset extends Zend_Validate_Abstract
{
    private $_charset = null;

    const INPUT_NOT_IN_WHITELIST = 'INPUT_NOT_IN_WHITELIST';
    const INVALID = 'INVALID';
    const DEFAULT_MESSAGE
        = 'At least one character of the input string was not an acceptable character.';

    protected $messageVar = null;
    protected $_messageVariables = array(
        'customMessage' => 'messageVar'
    );
    protected $_messageTemplates = array(
        self::INPUT_NOT_IN_WHITELIST => "%customMessage%",
        self::INVALID => 'Input must be a string of at least one character.'
    );


    /**
     * Sets validator options.
     *
     * @param string|array|Zend_Config $options Options to set for this validator.
     *
     * @return null
     */
    public function __construct($options = array())
    {
        if ($options instanceof Zend_Config) {

            $options = $options->toArray();

        } else if (!is_array($options)) {

            $options     = func_get_args();
            if (!empty($options)) {
                $temp['charsetstring'] = array_shift($options);
            }
            if (!empty($options)) {
                $temp['messagevar'] = array_shift($options);
            }

            $options = $temp;
        }

        if (!array_key_exists('charsetstring', $options)) {
            throw new Exception(
                'InvalidArgumemt to Custom_Validate_Charset constructor, ' .
                'expected a string charsetString.'
            );
        }

        $this->setCharsetString($options['charsetstring']);

        if (array_key_exists('messagevar', $options)) {
            $this->setMessageVar($options['messagevar']);
        } else {
            $this->setMessageVar();
        }

    }


    /**
     * Sets the whitelist of acceptable characters which may be from any character
     * sets representable by the UTF-8 set.
     *
     * @param string $charsetString The string of acceptable characters.
     *
     * @return null
     */
    public function setCharsetString($charsetString)
    {
        if (! is_string($charsetString)) {
            throw new Exception(
                'InvalidArgument to Custom_Validate_Charset setCharsetString,' .
                'expected a string charsetString'
            );
        }

        $detectedCharEnc = mb_detect_encoding($charsetString);

        if ($detectedCharEnc != 'UTF-8') {
            $charsetString = mb_convert_encoding(
                $charsetString, 'UTF-8', $detectedCharEnc
            );
        }

        $limit = mb_strlen($charsetString, 'UTF-8');
        $a = array();
        for ($i = 0; $i < $limit; $i++) {
            $a[] = mb_substr($charsetString, $i, 1, 'UTF-8');
        }
        $this->_charset = $a;
    }


    /**
     * Allows setting of a custom error message to be displayed when validation is
     * unsuccessful.  This method is called by the constructor to set the value to
     * the one defined in the configuration file.  A default message is set if one
     * isn't passed to this method.
     *
     * @param string $messageVar The error message to display.
     *
     * @return null
     */
    public function setMessageVar($messageVar = null)
    {
        if (is_string($messageVar) !== true || empty($messageVar)) {
            $messageVar = self::DEFAULT_MESSAGE;
        }
        $this->messageVar = $messageVar;
    }


    /**
     * Validates the input string against a whitelist of acceptable characters.
     *
     * @param string $input The input string to be validated.
     *
     * @return bool True if input string contains only characters defined in the
     *              whitelist, otherwise
     *              False.
     */
    public function isValid($input)
    {
        if (!is_string($input) || empty($input)) {
            $this->_error(self::INVALID);
            return false;
        }

        $canonical = ESAPI::getEncoder()->canonicalize($input, false);

        $detectedCharEnc = mb_detect_encoding($canonical);

        if ($detectedCharEnc != 'UTF-8') {
            $canonical = mb_convert_encoding($canonical, 'UTF-8', $detectedCharEnc);
        }

        $limit = mb_strlen($canonical, 'UTF-8');
        for ($i = 0; $i < $limit; $i++) {
            $c = mb_substr($canonical, $i, 1, 'UTF-8');
            if (in_array($c, $this->_charset, true) !== true) {
                $this->_error(self::INPUT_NOT_IN_WHITELIST);
                return false;
            }
        }
        return true;

    }
}
